#pragma once
#include <iostream>
#include <string>
#include <unordered_map>
#include <cstdlib>
#include <sys/epoll.h>
#include <unistd.h>

//先声明这两个类
class Event;
class Reactor;

typedef int (*callback_t)(Event* et);//callback_t是函数指针类型
class Event
{
public:
    int sock;//文件描述符

    //每一个文件描述符都有对应的缓冲区和回调方法
    std::string inbuffer;//输入缓冲区
    std::string outbuffer;//输出缓冲区

    callback_t recver;//读回调
    callback_t sender;//写回调
    callback_t errorer;//异常回调
    Reactor *R;// 设置Event回指Reactor对象的指针
public:
    Event()
        :sock(-1),recver(nullptr),sender(nullptr),errorer(nullptr),R(nullptr)
    {}

    //设置回调机制
    void RegisterCallback(callback_t _recver, callback_t _sender, callback_t _errorer)
    {
        recver = _recver;
        sender = _sender;
        errorer = _errorer;
    }
};


#define SIZE 128
#define NUM 64
class Reactor
{
private:
    int epfd;//epoll模型
    //若这里不指定std,会报错！！！ ‘unordered_map’ does not name a type
    std::unordered_map<int,Event*> events;//建立sock与Event结构的映射 
public:
    Reactor() :epfd(-1)
    {}
    ~Reactor()
    {
        close(epfd);
    }

    void InitReactor() //初始化Reactor
    {
        epfd = epoll_create(SIZE);//创建epoll模型
        if (epfd < 0)
        {
            std::cerr << "epoll_create error" << std::endl;
            exit(2);
        }
        std::cout << "InitReactor success" << std::endl;
    }

    //要保证套接字是有效的套接字 -> 在map中做查找工作,看能否查找到
    bool IsSockOk(int sock)
    {
        auto iter = events.find(sock);
        return iter != events.end();
    }

    void Dispatcher(int timeout) //事件派发函数
    {
        struct epoll_event revs[NUM];//将来就绪的事件都会被放在revs
        int n = epoll_wait(epfd, revs, NUM, timeout);//epoll_wait获取就绪事件
        for (int i = 0; i < n; i++)
        {
            int sock = revs[i].data.fd;//就绪的文件描述符
            uint32_t revents = revs[i].events;//就绪的事件内容

            //差错处理,将所有的错误问题全部转化成为让IO函数去解决
            //如果文件描述符有出错事件||文件描述符被挂断(对端关闭连接)  
            if ((revents & EPOLLERR) || (revents & EPOLLHUP)) 
                revents |= (EPOLLIN | EPOLLOUT);// 把所有出错事件归类成它是读事件或者写事件就绪

            if (revents & EPOLLIN) //读事件就绪
            {   
                //直接调用回调方法，执行对应的读取
                //events[sock]得到的就是文件描述符sock对应的Event结构体指针,然后调用它的回调方法
                //events[sock]->recver: 检测回调方法是否被设置过,如果被设置过就不是空指针
                if (IsSockOk(sock) &&events[sock]->recver )
                    events[sock]->recver(events[sock]);//调用读回调
            }

            if (revents & EPOLLOUT)//写事件就绪 
            {
                if (IsSockOk(sock) && events[sock]->sender)
                    events[sock]->sender(events[sock]);//调用写回调
            }
        }
    }

    bool InsertEvent(Event *evp, uint32_t evs) //参数:文件描述符对应的Event结构 事件集合
    {
        // 1. 将evp中的sock添加到epoll模型中
        struct epoll_event ev;
        ev.events = evs;//要关心的事件集合
        ev.data.fd = evp->sock;//文件描述符
        if (epoll_ctl(epfd, EPOLL_CTL_ADD, evp->sock, &ev) < 0)//放入epoll模型当中
        {
            std::cerr << "epoll_ctl add event failed" << std::endl;
            return false;
        }
        std::cout << "添加: " << evp->sock << " 到epoll模型中,成功" << std::endl;
        
        // 2. 将ev本身插入到unordered_map中,建立sock与EventItem结构的映射关系
        events.insert({evp->sock, evp});
        return true;
    }

    void DeleteEvent(Event *evp) //删除一个事件
    {
        int sock = evp->sock;
        auto iter = events.find(sock);
        if (iter != events.end())
        {
            epoll_ctl(epfd, EPOLL_CTL_DEL, sock, nullptr);//将该文件描述符从epoll模型中删除
            events.erase(iter);//取消sock与EventItem结构的映射关系
            close(sock);//关闭该文件描述符的连接
            delete evp;//4. 删除event节点！！！因为是new出来的
        }
    }

    bool EnableRW(int sock, bool enbread, bool enbwrite) //使能接口,把读写打开
    {
        struct epoll_event ev;
        //ET模式 | 是否关心读事件 | 是否关心写事件
        ev.events = EPOLLET | (enbread ? EPOLLIN : 0) | (enbwrite ? EPOLLOUT : 0);
        ev.data.fd = sock;

        if (epoll_ctl(epfd, EPOLL_CTL_MOD, sock, &ev) < 0) //EPOLL_CTL_MOD:修改 
        {
            std::cerr << "epoll_ctl mod event failed" << std::endl;
            return false;
        }
        return true;
    }
};


